#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
-------------------------------------------------------------------------------
@NAME          : dodir.py
@TIME          : 2025/01/31 16:30:10
@AUTHOR        : chenlip
@VERSION       : 0.0.1
@DESCRIPTION   : 阿里云盘文件处理
说明:
    1. 下载文件本地目录
    2. 上传文件到指定文件夹
    3. 通过递归, 找出指定目录下的所有的空文件夹
    4. 使用递归，找出指定目录下指定深度的所有文件夹目录树
    5. 获取某个相册下面的所有文件

    driveid: drivename: drivetype
    252470150 Default normal
    317570150 alibum normal
    352470150 Default normal # 这个是默认的
    500552972 resource normal # 这个是备份系统的driveid
    551635121 sharedAlbum normal
    89106557 note_drive normal

    2025-2-2 aligo6.2.4 不能实现的功能
    1- 相册文件只能使用增加到新相册，不能移动到新相册
    2- 从一个相册增加到另一个相册，file_id是不会变化的，如果删除（或移除）原相册的文件，新相册的文件也会被删除
    3- 如果另一个相册存在相同id的文件，增加到新相册时，不会报错，但是会增加失败
    4- 默认相册中只能移除到回收站，不能‘从相册中移除’
    5- 相册文件下载本地时，文件日期会变化为当前下载日期时间，无法保持原文件云端创建日期

    功能设计思路：
    1- 从默认相册中获取文件列表，将文件移动到文件云端创建日期的相册中
    2- 获取相册列表，如果相册名称是日期，则将该相册的文件下载到本地的日期文件夹中
        2.1- 获取相册列表，如果相册名称是日期，先获取该相册的文件列表，并生成文件列表txt文件（将每个文件的信息写入到txt，每行一个文件信息）
        2.2- 读取txt文件，根据文件云端创建时间进行DESC排序，在txt文件中根据排序在对应的文件名称中添加序号（从10001-99999），
        2.3- 读取txt文件，下载文件到本地日期文件夹中，并将文件名称改为序号+"-"+文件名称
    3- 获取相册列表，如果相册名称是日期，则将该相册的文件移动到备份系统中
    
-------------------------------------------------------------------------------
'''

# --------------------------引用--------------------------
import os, time, random

from aligo import Aligo
from typing import Optional, List
from app.common.utils import Utils as ut
from app.common.file_utils import FileSetter as fs
from app.common.logutil import log as logger

# --------------------------引用--------------------------

class AliyunDriveAct:

    V3_FILE_DELETE = '/v3/file/delete'
    V1_FILE_DELETE = '/adrive/v1/file/delete'
    V2_ALBUM_FILE_DELETE = '/adrive/v1/album/delete_files'
    

    def __init__(self, temp_dir:str=None, refresh_token:str=None):
        # 初始化阿里云盘客户端
        self.alidrgo = Aligo() # 第一次使用，会弹出二维码，供扫描登录

        self.album_drive_id = '317570150' # 相册id

        self.bak_drive_id = '352470150' # 备份系统的存储位置
        self.default_drive_id = '500552972' # 系统的默认存储位置

        # 设置本地的临时目录
        temp_dir = os.path.join(temp_dir, ut.CURDAY) if temp_dir else os.path.join(ut.TEMPDIR, ut.CURDAY) # 如果未指定临时目录，则默认为static/temps/当天日期
        os.makedirs(temp_dir, exist_ok=True)
        
        self.temp_dir = temp_dir

    def remove_file_from_album(self, album_id: str, file_id: str) -> bool:
        """移除相册文件，
            阿里云盘规则：
                如果是从默认相册中转移过来的文件，移除相册，文件不会被删除，只是从相册中移除
        {
            "album_id": "fBAgnmZsxSa",
            "drive_file_list": [
                {
                    "drive_id": "317570150",
                    "file_id": "676233b22f4b615ce0cf4922b4086e74c227474f"
                }
            ]
        }
        """
        drive_id = drive_id or self.default_drive_id
        response = self.alidrgo.post(self.V2_ALBUM_FILE_DELETE, body={
            "album_id": album_id,
            "drive_file_list": [
                {
                    "drive_id": self.album_drive_id,
                    "file_id": file_id
                }
            ]
        })
        return response.status_code == 204

    # 获取默认相册下的所有文件
    def get_default_album_files(self, maxcount: int = 0):
        '''
            获取默认相册下的所有文件
            :Args:
                - maxcount: 最大获取文件数量，默认为0，获取所有文件
            :Return:
                - 返回文件列表，包含[文件ID, 文件名称, 文件扩展名, 文件大小, 创建时间, 更新时间]
        '''
        # 排序参数 DESC: 降序，从大到小/从新到旧；ASC: 升序，从小到大/从旧到新
        _f_list = []
        fn_list = self.alidrgo.get_file_list(
            parent_file_id='root', 
            drive_id=self.album_drive_id,
            category='image',
            order_by='created_at',
            order_direction='DESC'
            )
        maxcount = 999999 if maxcount == 0 else maxcount
        _n = 0
        for fn in fn_list:
            _n += 1
            if _n <= maxcount:
                _t = ut.BASE_IMAGE_FILE.copy()
                _l_time = self.get_album_file_detail(fn.file_id)
                _t['file_id'] = fn.file_id
                _t['fname'] = fn.name
                _t['ext'] = fn.file_extension
                _t['size'] = fn.size
                _t['box'] = [_l_time[2], _l_time[3]]
                _t['created_at'] = ut.iso8601_localtime(fn.created_at)
                _t['updated_at'] = ut.iso8601_localtime(fn.updated_at)
                _t['local_created_at'] = ut.iso8601_localtime(_l_time[0])
                _t['local_modified_at'] = ut.iso8601_localtime(_l_time[1])
                _f_list.append(_t)
                
            else:
                break

        # 按本地创建时间倒序排序
        _f_list.sort(key=lambda x: x['local_created_at'], reverse=True)
        
        return _f_list
        
    # 获取某个相册（相册id：sHsaRPskbb3）下面的所有文件，其他相册ID ['NE94k43WH3C', 'ZTzwkjj894H', 'sHsaRPskbb3']
    def get_album_files(self, album_id:str, maxcount: int = 0):
        '''
            获取某个相册下的所有文件
            :Args:
                - album_id: 相册ID
                - maxcount: 最大获取文件数量，默认为0，获取所有文件
            :Return:
                - 返回文件列表，包含[文件ID, 文件名称, 文件扩展名, 文件大小, 创建时间, 更新时间]
        '''
        # 排序参数 DESC: 降序，从大到小/从新到旧；ASC: 升序，从小到大/从旧到新
        _f_list = []
        fn_list = self.alidrgo.list_album_files(album_id=album_id)
        maxcount = 999999 if maxcount == 0 else maxcount
        _n = 0
        for fn in fn_list:
            _n += 1
            if _n <= maxcount:
                _t = ut.BASE_IMAGE_FILE.copy()
                _l_time = self.get_album_file_detail(fn.file_id)
                _t['file_id'] = fn.file_id
                _t['fname'] = fn.name
                _t['file_extension'] = fn.file_extension
                _t['size'] = fn.size
                _t['box'] = [_l_time[2], _l_time[3]]
                _t['created_at'] = ut.iso8601_localtime(fn.created_at)
                _t['updated_at'] = ut.iso8601_localtime(fn.updated_at)
                _t['local_created_at'] = ut.iso8601_localtime(_l_time[0])
                _t['local_modified_at'] = ut.iso8601_localtime(_l_time[1])
                _f_list.append(_t)
                
            else:
                break

        # 按本地创建时间倒序排序
        _f_list.sort(key=lambda x: x['local_created_at'], reverse=True)
        
        return _f_list
    
    # 获取相册文件的本地创建时间，文件的宽高；
    def get_album_file_detail(self, file_id:str):
        '''
            获取相册文件的本地创建时间
            :Args:
                - file_id: 文件ID
            :Return:
                - 返回文件的本地创建时间
        '''
        _f_l_time_box = ()
        file_info = self.get_file_info(file_id)
        try:
            _f_l_ctime = file_info.local_created_at
            _f_l_mtime = file_info.local_modified_at
            _f_l_width = file_info.width
            _f_l_height = file_info.height
            _f_l_time_box = (_f_l_ctime, _f_l_mtime, _f_l_width, _f_l_height)
            print(f"获取文件本地创建时间正常: {_f_l_ctime}")
        except Exception as e:
            _f_l_ctime = file_info.created_at
            _f_l_mtime = file_info.updated_at
            _f_l_time_box = (_f_l_ctime, _f_l_mtime, 0, 0)
            logger.error(ut.cco(f"获取文件({file_info})本地创建时间异常, 返回云端创建时间: {str(e)}", "red"))
            # 忽略异常，使用pass继续执行程序
            pass
        
        return _f_l_time_box

    # 获取相册文件信息
    def get_file_info(self, file_id:str):
        '''
            获取文件信息
            :Args:
                - file_id: 文件ID
                - drive_id: 阿里云盘ID, 默认为相册ID;self.album_drive_id
            :Return:
                - 返回文件的详细信息字典
        '''
        file_info = self.alidrgo.get_file(file_id=file_id, drive_id=self.album_drive_id)
        if not file_info:
            raise ValueError(f"阿里云盘上文件没有找到 with ID: {file_id}")
        
        return file_info

    def dl_file(self, file_id: str, drive_id:str, local_path: Optional[str] = None) -> bool:
        """
        下载指定文件ID的文件
        :Args:
            - file_id: 阿里云盘文件ID
            - drive_id: 阿里云盘ID
            - local_path: 本地保存路径，默认为None（保存在当前目录）, 如果指定目录不存在，则会自动创建到self.temp_dir(默认为static/temps/当天日期/downloads)
            - file_index: 文件索引，用于排序文件，默认为None
        :Return: 
            下载是否成功
        """
        isok = False
        max_retries = 5
        retry_count = 0
        
        while retry_count < max_retries:
            try:
                # 获取文件信息
                file = self.alidrgo.get_file(file_id=file_id, drive_id=drive_id)
                if not file:
                    print(f"File not found with ID: {file_id}")
                    isok = False
                    local_path = "文件不存在"
                    return isok, local_path
                
                # 文件的创建日期
                _f_c_date = ut.iso8601_localtime(file.created_at, '%Y-%m-%d')

                # 下载文件
                temp_dl_dir = os.path.join(self.temp_dir, 'aliyun_downloads')
                os.makedirs(temp_dl_dir, exist_ok=True)

                local_path = temp_dl_dir if not local_path else local_path # 不指定目录时，默认下载到临时目录（static/temps/当天日期/downloads）

                result = self.alidrgo.download_file(file=file, local_folder=local_path)
                if result:
                    _t_fn_path = os.path.join(local_path, file.name)
                    print(f"成功下载: {_t_fn_path}, 并暂停2秒")
                    # 修改文件创建时间
                    fs.set_creation_time(_t_fn_path, _f_c_date)
                    isok = True
                    time.sleep(2) # 停顿2秒
                    break
                else:
                    print(ut.cco("下载失败...", "red"))
                    isok = False
                    retry_count += 1

            except Exception as e:
                retry_count += 1
                if retry_count < max_retries:
                    print(ut.cco(f"下载异常，正在进行第{retry_count}次重试: {str(e)}", "red"))
                    time.sleep(2) # 重试前等待2秒
                else:
                    local_path = "重试5次后，依然下载异常"
                    print(ut.cco(f"已重试{max_retries}次仍然失败，文件ID-{file_id}: {str(e)}", "red"))
                    isok = False
        
        return isok, local_path
    
    # 上传文件
    def ul_file(self, file_path: str, folder_id: str) -> bool:
        """
        上传文件到指定文件夹
        :Args:
            - file_path: 本地文件路径
            - folder_id: 阿里云盘文件夹ID
        :Return: 
            上传是否成功
        """
        isok = False
        try:
            # 上传文件
            result = self.alidrgo.upload_file(file_path=file_path, folder_id=folder_id)
            if result:
                print(f"Successfully uploaded: {file_path}")
                isok = True
            else:
                print("Upload failed")
                isok = False

        except Exception as e:
            print(f"Error uploading file: {str(e)}")
            isok = False
        
        return isok
    
    def get_aligo_dir_path(self, ali_dir_lst:list=[]):
        '''
            通过get_path()方法, 获取文件夹的路径
        '''
        _aligo_dir = ""
        # 列表倒序循环
        for _dir in ali_dir_lst[::-1]:
            _dir_type = _dir.type
            _dir_name = _dir.name
            if _dir_type == "folder":
                _aligo_dir = _aligo_dir + _dir_name + "/"

        return _aligo_dir

    # 递归阿里云目录树, 找出所有的文件夹
    def find_all_dir_tree(self, parent_file_id:str='root', depth:int=0):
        '''
            递归阿里云目录树, 找出所有的文件夹
            如果文件夹名称与空目录的名称相同, 则将下载这个文件夹
        '''

        ali = self.alidrgo # 第一次使用，会弹出二维码，供扫描登录

        # 停顿几秒
        delay = random.randint(3, 6) # 延迟3-6秒
        time.sleep(delay)
        depth += 1 
        file_list = ali.get_file_list(parent_file_id=parent_file_id)
        
        for file in file_list:
            
            _fo_name = file.name
            _fo_extens = file.file_extension
            _fo_type = file.type
            _fo_id = file.file_id
            
            print("="*depth, _fo_name, _fo_id)

            if (_fo_type == 'folder' and depth<4): # 只遍历两次
                _fo_path = self.get_aligo_dir_path(ali.get_path(_fo_id).items)
                print("阿里云盘目录路径:", _fo_path)
                # 递归
                self.find_all_dir_tree(_fo_id, depth)

    # 创建相册，如果相册同名，则返回相册ID
    def create_album(self, album_name:str, album_desc:str=None):
        '''
            创建相册，如果相册同名，则返回相册ID, 同时将相册列表信息写入到文本文件
            :Args:
                - album_name: 相册名称
                - album_desc: 相册描述
            :Return:
                - 返回相册对象
        '''
        _is_exit_album = False
        _tmp_lst = []
        _album_item = None

        _txtlstfn = os.path.join(self.temp_dir, "album_list_"+ut.CURDAY+".txt")
        if not os.path.exists(_txtlstfn): # 如果文件不存在
            print("相册列表文件不存在，从新获取相册列表...")
            _albumlist = self.alidrgo.list_albums()
            
            for _al in _albumlist:
                _tmp_lst.append([_al.album_id, _al.name])

        else:
            _tmp_lst = ut.r_w_lst_txt(act='r', txtfnpath=_txtlstfn)

        for _album in _tmp_lst:
            if str(_album[1]).strip() == album_name: # 判断是否存在已有的相册
                print(f"相册{album_name}已经存在，返回相册ID: {_album[0]}")
                _album_item = _album
                _is_exit_album = True # 已存在同名相册
                break
        
        if not _is_exit_album: # 如果不存在同名相册，则创建新的相册
            _album_item = self.alidrgo.create_album(album_name, album_desc)
            # 将新创建列表加入到文本文件
            _tmp_lst.append([_album_item.album_id,_album_item.name]) 
        
            print("写入相册列表:", _tmp_lst)

            _txtfn = ut.r_w_lst_txt(act='w', txtfnpath=_txtlstfn, txtlst=_tmp_lst)

        return _album_item

    # 处理单个相册文件,将某个文件加入到特定日期的相册中；
    def add_file_to_album(self, file_id:str):
        '''
            处理阿里云盘单个相册文件，逻辑如下：
            1- 查找文件
            2- 将阿里云盘相册图片转移到指定相册(创建一个图片日期的相册，将文件移动到该相册)

            前置条件：
            1- 相册device_id已经存在

        '''

        # 获取文件信息
        file = self.alidrgo.get_file(file_id=file_id, drive_id=self.album_drive_id)
        if not file:
            print(f"文件没有找到 with ID: {file_id}")
            return

        _f_c_date = ut.iso8601_localtime(file.created_at, '%Y-%m-%d')
        _bak_album = self.create_album(_f_c_date, f"备份{_f_c_date}头条图片")
        
        _bak_album_id = '' # 'sHsaRPskbb3'

        # 3- move原相册文件到指定目录
        if _bak_album:
            # 判断_bak_album是否为列表
            if isinstance(_bak_album, list):
                _bak_album_id = str(_bak_album[0])  # Assume album ID is first element 
                print(f"使用已有的 {_f_c_date} 备份相册: {_bak_album_id} ；相册名：{_bak_album[1]}")
            else:
                _bak_album_id = _bak_album.album_id
                print(f"新创建 {_f_c_date} 备份相册成功: {_bak_album_id}；相册名：{_bak_album.name}")

            _add_result = self.alidrgo.add_files_to_album(album_id=_bak_album_id, files=[file])
            
            if len(_add_result)>0:
                print(f"将原文件ID: {file_id}  添加到 {_f_c_date} 备份相册成功,新文件ID{_add_result[0].file_id} \n")
            else:
                print(ut.cco(f"将原文件ID: {file_id} 增加到到备份相册失败", "red"), _add_result)
        else:
            print(ut.cco(f"创建{ut.CURDAY}备份相册失败", "red"))







